package cc.telecomdigital.tdstock.Services;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.URL;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;

import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;


import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;

//import org.ksoap2.HeaderProperty;
//import org.ksoap2.HeaderProperty;
import org.ksoap2.SoapEnvelope;
import org.ksoap2.serialization.SoapSerializationEnvelope;
import org.ksoap2.transport.HttpTransportSE;
//import org.kxml2.io.KXmlParser;
//import org.kxml2.io.KXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;

import cc.telecomdigital.tdstock.Framework.UniqueItem.TDStockLog;

import android.content.Context;

public class SoapTransport
{	
	private String urlString;
	private int    communicTimeout;
	private boolean useSSL;
	private Context ownerContext;
	
	private int   SSLCertFileID;
	private final boolean debugMode = false;
	private final String LOG_TAG = "SoapTransport";
	
	private final int maxBufferSize = 8192;
	
	/** Create a SoapTransport for non SSL
	 * @param iContext - UI Context
	 * @param iURL		- URL
	 * @param iTimeout - Timeout
	 */
	public SoapTransport(Context iContext, String iURL, int iTimeout)
	{
		urlString       = iURL;
		communicTimeout = iTimeout;
		useSSL          = false;
		ownerContext    = iContext;
		SSLCertFileID   = 0;
	}
	
	/** Create a SoapTransport object for SSL
	 * @param iContext - UI Context
	 * @param iURL	    - urlToGo 
	 * @param iTimeout - Timeout for protocol
	 * @param iSSLCertFileID - the store ID in your resource file, the certificate
	 */
	public SoapTransport(Context iContext, String iURL, int iTimeout, int iSSLCertFileID)
	{
		urlString       = iURL;
		communicTimeout = iTimeout;
		useSSL          = true;
		ownerContext    = iContext;
		SSLCertFileID   = iSSLCertFileID;
	}	
	
	public void SendData( String iServiceName, SoapSerializationEnvelope envelope) 
					throws Exception, java.net.SocketTimeoutException, XmlPullParserException 
					
	{		
		if (useSSL)
		{	
			//using the self override object, this is for SSL only	
//			callLSoap(iServiceName, envelope, null);  //HttpURLConnection, modified on HTTP Connection
			callLSelf(iServiceName, envelope, null, SSLCertFileID);  //HttpClient , modified on Socket Level
		}			
		else
		{	
			HttpTransportSE dataTransfer;
			
			dataTransfer = new HttpTransportSE( urlString, communicTimeout);				
			dataTransfer.call(iServiceName, envelope);			
		}
	}
	
//	//SSL Connection
//	protected ServiceConnectionSE dgetServiceConnection() throws IOException, Exception
//	{
//		return new ServiceConnectionSE(urlString, communicTimeout);
//	}

//	public void call(String soapAction, SoapEnvelope envelope)	
//			throws IOException, XmlPullParserException, 
//				KeyStoreException, NoSuchAlgorithmException, CertificateException, 
//				UnrecoverableKeyException, KeyManagementException
//	{
//		callLSoap(soapAction, envelope, null);
////		callLSelf(soapAction, envelope, null);		
//	}
	
	private String xmlVersionTag = "";
	
	@SuppressWarnings("rawtypes")
	private List callLSelf(String soapAction, SoapEnvelope envelope, List headers, int rawCertLocation)
		throws IOException, XmlPullParserException, KeyStoreException, NoSuchAlgorithmException, 
			CertificateException, UnrecoverableKeyException, KeyManagementException
	{		
		// initialize SSLSocketFactory to use the certificates
		SoapSSLSocketFactory sslFact;
		
		// load trust store certificate
		if ((rawCertLocation != 0) && (ownerContext != null))
		{
			InputStream clientTruststoreIs = ownerContext.getResources().openRawResource(rawCertLocation);
			KeyStore trustStore = null;
			trustStore = KeyStore.getInstance("BKS");
			trustStore.load(clientTruststoreIs, "mysecret".toCharArray());
			clientTruststoreIs.close();
			if (debugMode)
				TDStockLog.i(LOG_TAG, "Loaded server certificates: " + trustStore.size());

			// initialize trust manager factory with the read trust store
			TrustManagerFactory trustManagerFactory = null;
			trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
			trustManagerFactory.init(trustStore);

			// setup client certificate
			// load client certificate
//			InputStream keyStoreStream = new BufferedInputStream(new FileInputStream(TradeAppInfo.GetFullClientCertFileName(ownerContext)));
//			KeyStore keyStore = null;
//			keyStore = KeyStore.getInstance("BKS");
//			keyStore.load(keyStoreStream, "passphrase".toCharArray());
//			keyStoreStream.close();
//			if (debugMode)
//		 		Log.i(LOG_TAG, "Loaded client certificates: " + keyStore.size());

//			// initialize key manager factory with the read client certificate
//			KeyManagerFactory keyManagerFactory = null;
//			keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
//			keyManagerFactory.init(keyStore, "passphrase".toCharArray());
		

			sslFact = new SoapSSLSocketFactory(trustStore);
			sslFact.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);		
		}
		else
		{
			sslFact = new SoapSSLSocketFactory(null);
			sslFact.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
		}
		
		// Set basic data
		HttpParams params = new BasicHttpParams();
		//changes strictly target for smart tone
		//HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
		HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_0);
		HttpProtocolParams.setContentCharset(params, "UTF-8");
		HttpProtocolParams.setUseExpectContinue(params, true);
		HttpProtocolParams.setUserAgent(params, "Android app/1.0.0");

//		// Make pool
//		ConnPerRoute connPerRoute = new ConnPerRouteBean(12);
//		ConnManagerParams.setMaxConnectionsPerRoute(params, connPerRoute);
//		ConnManagerParams.setMaxTotalConnections(params, 20);

		// Set timeout
		HttpConnectionParams.setStaleCheckingEnabled(params, false);
		HttpConnectionParams.setConnectionTimeout(params, communicTimeout);
		HttpConnectionParams.setSoTimeout(params, communicTimeout);
		HttpConnectionParams.setSocketBufferSize(params, maxBufferSize);

		// Some client params
		HttpClientParams.setRedirecting(params, false);

		// Register http/s shemas!
		SchemeRegistry schReg = new SchemeRegistry();
		schReg.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
		schReg.register(new Scheme("https", sslFact, 443));
		
		ClientConnectionManager conMgr;
		DefaultHttpClient sClient;
		HttpPost httpPost;
		HttpEntity entity;
		
		conMgr = new ThreadSafeClientConnManager(params, schReg);
		if (rawCertLocation != 0)
			sClient = new DefaultHttpClient(conMgr, params);
		else
			sClient = new DefaultHttpClient();
		
		entity = new StringEntity(createRequestDataInString(envelope));

		httpPost = new HttpPost(urlString);
		httpPost.setEntity(entity);
		      		
		httpPost.addHeader("User-Agent", "kSOAP/2.0");
		//SOAPAction is not a valid header for VER12 so do not add it
		// @see "http://code.google.com/p/ksoap2-android/issues/detail?id=67
		if (envelope.version != SoapSerializationEnvelope.VER12)
		{
			httpPost.addHeader("SOAPAction", soapAction);
		}
		httpPost.addHeader("Content-Type", "text/xml;charset=UTF-8");
		httpPost.addHeader("Connection", "keep-alive");
		//Somehow, adding this will ends up with duplicate Content-length
//		httpPost.addHeader("Content-Length", "" + httpPost.getEntity().getContentLength());
		
		
		HttpResponse response = sClient.execute(httpPost);
		HttpEntity httpEntity = response.getEntity();

		InputStream is = httpEntity.getContent();
		
		//Debug for reading the read date, like "internal error"
//		BufferedReader read = new BufferedReader(new InputStreamReader(is));
//		String query = null;
//		while ((query = read.readLine()) != null)
//			System.out.println(query);	
	      
		parseResponse(envelope, is);
		
		return null;
	}
	
	@SuppressWarnings("rawtypes")
	private List callLSoap(String soapAction, SoapEnvelope envelope, List headers) 
				throws IOException, XmlPullParserException
	{
		if (soapAction == null)
			soapAction = "\"\"";

		ServiceConnectionSE  connection;
		byte[] requestData;
		
		connection  = null;
		requestData = createRequestData(envelope);
		try
		{			
			connection = new ServiceConnectionSE(urlString, communicTimeout);
		}
		catch(Exception io)
		{
			
		}
		
		if (connection == null)
			return null;
				
		connection.setRequestProperty("User-Agent", "kSOAP/2.0");
		//SOAPAction is not a valid header for VER12 so do not add it
		// @see "http://code.google.com/p/ksoap2-android/issues/detail?id=67
		if (envelope.version != SoapSerializationEnvelope.VER12)
		{
			connection.setRequestProperty("SOAPAction", soapAction);
		}
		connection.setRequestProperty("Content-Type", "text/xml");
		//connection.setRequestProperty("Connection", "close");
		connection.setRequestProperty("Connection", "keep-alive");		
		connection.setRequestProperty("Content-Length", "" + requestData.length);
	    
		// Pass the headers provided by the user along with the call
	    	if (headers != null)
	    	{
			for (int i = 0; i < headers.size(); i++)
			{
				HeaderProperty hp = (HeaderProperty) headers.get(i);
				connection.setRequestProperty(hp.getKey(), hp.getValue());
			}
	    }
	    
		connection.setRequestMethod("POST");
		connection.connect();

		OutputStream os = connection.openOutputStream();
   
		os.write(requestData, 0, requestData.length);
		os.flush();
		os.close();
		requestData = null;
		InputStream is;
		
		List retHeaders = null;
	    
		try
		{
			connection.connect();
			is = connection.openInputStream();
			retHeaders = connection.getResponseProperties();
		}
		catch (IOException e)
		{
			is = connection.getErrorStream();
			if (is == null)
			{
				connection.disconnect();
				throw (e);
			}
		}
       
		parseResponse(envelope, is);
		
		return retHeaders;

	}
	
	protected byte[] createRequestData(SoapEnvelope envelope) throws IOException
	{
		return createRequestDataSub(envelope).toByteArray();
	}
	
	protected String createRequestDataInString(SoapEnvelope envelope) throws IOException
	{				
		return createRequestDataSub(envelope).toString();
	}	
	
	private ByteArrayOutputStream createRequestDataSub(SoapEnvelope envelope) throws IOException
	{
		ByteArrayOutputStream bos;
		
		bos = new ByteArrayOutputStream();
		bos.write(xmlVersionTag.getBytes());
		XmlSerializer xw = new KXmlSerializer();
		xw.setOutput(bos, null);
		envelope.write(xw);
		xw.flush();
		bos.write('\r');
		bos.write('\n');
		bos.flush();
				
		return bos;
	}	
	
	private void parseResponse(SoapEnvelope envelope, InputStream is)
		throws XmlPullParserException, IOException
	{
		XmlPullParser xp = new KXmlParser();
		xp.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
		xp.setInput(is, null);
		envelope.parse(xp);
	}	
	
	public class ServiceConnectionSE
	{
		private HttpsURLConnection connection;
		public ServiceConnectionSE(String url, int iTimeoutValue) throws IOException, Exception
		{		
			try
			{												   
				SSLContext sc = SSLContext.getInstance("TLS");				
				sc.init(null, trustAllCerts, new java.security.SecureRandom());
				
				HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
			}
			catch (Exception e)
			{
				TDStockLog.e(LOG_TAG, "ServiceConnection=>Open Socket Fail: " + e.toString());
				e.getMessage();
			}		
			
			connection = (HttpsURLConnection)new URL(url).openConnection();
			((HttpsURLConnection) connection).setHostnameVerifier(new HostnameVerifier()
			{
				public boolean verify(String hostname, SSLSession session)
				{
					if (hostname.equals("mobilewsdev.mango.cc"))
						return true;
				
					if (debugMode)
						TDStockLog.i(LOG_TAG, "Failed: HostName=" + hostname);					
					return false;						
				}
			});
			 
			connection.setUseCaches(false);
			connection.setDoOutput(true);
			connection.setDoInput(true);
			connection.setConnectTimeout(iTimeoutValue);
			connection.setReadTimeout(iTimeoutValue); 
			//even if we connect fine we want to time out if we cant read anything..
		}

		private TrustManager[] trustAllCerts = new TrustManager[]
		{
			new X509TrustManager()
			{
				public java.security.cert.X509Certificate[] getAcceptedIssuers()
				{
			            return null;
				}
				public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType)
				{
				}
				public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType)
				{
				}
			}
		};		
		 
		public void connect() throws IOException
		{
			connection.connect();
		}

		public void disconnect()
		{
			connection.disconnect();
		}

		@SuppressWarnings({ "rawtypes", "unchecked" })
		public List getResponseProperties()
		{
			Map properties = connection.getHeaderFields();
			Set keys = properties.keySet();
			List retList = new LinkedList();
		    	
		    	for (Iterator i = keys.iterator(); i.hasNext();)
		    	{
		    		String key = (String) i.next();
		    		List values = (List) properties.get(key);
		    		
		    		for (int j = 0; j < values.size(); j++)
		    		{
		    			retList.add(new HeaderProperty(key, (String) values.get(j)));
		    		}
		    	}
		    	
			return retList;
		}

		public void setRequestProperty(String string, String soapAction)
		{
			connection.setRequestProperty(string, soapAction);
		}

		public void setRequestMethod(String requestMethod) throws IOException
		{
			connection.setRequestMethod(requestMethod);
		}

		public OutputStream openOutputStream() throws IOException
		{
			return connection.getOutputStream();
		}

		public InputStream openInputStream() throws IOException
		{
			return connection.getInputStream();
		}

		public InputStream getErrorStream()
		{
			return connection.getErrorStream();
		}

		public String getHost()
		{
			return connection.getURL().getHost();
		}

		public int getPort()
		{
			return connection.getURL().getPort();
		}

		public String getPath()
		{
			return connection.getURL().getPath();
		}	
	}
		
	public class SoapSSLSocketFactory extends SSLSocketFactory
	{
		SSLContext sslContext = SSLContext.getInstance("TLS");

		public SoapSSLSocketFactory(KeyStore truststore) 
			throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException
		{
			super(truststore);

			TrustManager tm = new X509TrustManager()
			{
				public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException
				{					
					int count, index;
					
					count = chain.length;
					for (index = 0; index < count; index++)
					{
						if (debugMode)						
							TDStockLog.i(LOG_TAG, index + ": " + chain[index].getType().toString());
					}
					
				}

				public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException
				{
					if (debugMode)
						TDStockLog.i(LOG_TAG, "checkServerTrusted");					
				}

				public X509Certificate[] getAcceptedIssuers()
				{				
					return null;
				}
			};

			sslContext.init(null, new TrustManager[] { tm }, null);

		}

		@Override
		public Socket createSocket(Socket socket, String host, int port, boolean autoClose) 
				throws IOException, UnknownHostException
		{
			return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
		}

		@Override
		public Socket createSocket() throws IOException
		{
			return sslContext.getSocketFactory().createSocket();
		}
	}
	
	//Internal defined for header property
	/* Source from a direct copy of KSoap 2.5.7 */
	public class HeaderProperty
	{
		private String key;
		private String value;
		
		public HeaderProperty(String key, String value)
		{
			this.key = key;
			this.value = value;
		}
		
		public String getKey()
		{
			return key;
		}
		
		public void setKey(String key)
		{
			this.key = key;
		}
		
		public String getValue()
		{
			return value;
		}
		
		public void setValue(String value)
		{
			this.value = value;
		}
	}
	
}